'
' Will Strohl (will.strohl@gmail.com)
' http://www.willstrohl.com
'
'Copyright (c) 2010, Will Strohl
'All rights reserved.
'
'Redistribution and use in source and binary forms, with or without modification, are 
'permitted provided that the following conditions are met:
'
'Redistributions of source code must retain the above copyright notice, this list of 
'conditions and the following disclaimer.
'
'Redistributions in binary form must reproduce the above copyright notice, this list 
'of conditions and the following disclaimer in the documentation and/or other 
'materials provided with the distribution.
'
'Neither the name of Will Strohl nor the names of its contributors may be 
'used to endorse or promote products derived from this software without specific prior 
'written permission.
'
'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
'EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
'OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
'SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
'INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
'TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
'BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
'CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
'ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
'DAMAGE.
'

Imports DotNetNuke.Common.Utilities
Imports DotNetNuke.Modules.Blog.Business
Imports System.Globalization
Imports System.Text
Imports System.Text.RegularExpressions

Namespace WillStrohl.Utilities.BlogRss

    Public Class BlogRssHandler
        Implements System.Web.IHttpHandler

#Region " Private Members "

        Private p_PortalId As Integer = Null.NullInteger
        Private p_BlogId As Integer = Null.NullInteger
        Private p_CategoryId As Integer = Null.NullInteger
        Private p_EntryLimit As Integer = Null.NullInteger
        Private p_FeedOutput As String = Null.NullString
        Private p_RemoveCopyright As Boolean = False
        Private p_BlogXmlCacheTime As Integer = Null.NullInteger

#End Region

#Region " Properties "

        Private ReadOnly Property PortalId() As Integer
            Get
                Return Me.p_PortalId
            End Get
        End Property

        Private ReadOnly Property BlogId() As Integer
            Get
                Return Me.p_BlogId
            End Get
        End Property

        Private ReadOnly Property CategoryId() As Integer
            Get
                Return Me.p_CategoryId
            End Get
        End Property

        Private ReadOnly Property EntryLimit() As Integer
            Get
                Return Me.p_EntryLimit
            End Get
        End Property

        Private ReadOnly Property FeedOutput() As String
            Get
                Return Me.p_FeedOutput
            End Get
        End Property

        Private ReadOnly Property RemoveCopyright() As Boolean
            Get
                Return Me.p_RemoveCopyright
            End Get
        End Property

        Private ReadOnly Property BlogXmlCacheTime() As Integer
            Get
                If Me.p_BlogXmlCacheTime > 0 Then
                    Return Me.p_BlogXmlCacheTime
                End If

                Dim obj As Object = Nothing
                Try
                    obj = System.Configuration.ConfigurationManager.AppSettings("BlogXmlCacheTime")
                    If Not obj Is Nothing Then
                        Me.p_BlogXmlCacheTime = Integer.Parse(obj.ToString, NumberStyles.Integer)
                        If Me.p_BlogXmlCacheTime < 1 Or Me.p_BlogXmlCacheTime > 20 Then
                            Throw New ArgumentOutOfRangeException("AppSettings.BlogXmlCacheTime")
                        End If
                    End If
                Catch ex As Exception
                    DotNetNuke.Services.Exceptions.LogException(ex)
                    Me.p_BlogXmlCacheTime = 5
                End Try

                Return Me.p_BlogXmlCacheTime
            End Get
        End Property

#End Region

        ''' <summary>
        ''' ProcessRequest - this is the default method called by the handler
        ''' </summary>
        ''' <param name="context"></param>
        ''' <remarks></remarks>
        ''' <history>
        ''' [wstrohl] - 20100604 - Making the response end if the portalid is not found
        ''' </history>
        Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) Implements System.Web.IHttpHandler.ProcessRequest
            context.Response.ContentType = "text/xml"

            '
            ' Validate and load the local members
            '

            Dim obj As Object = context.Request.Params("portalid")
            If Not obj Is Nothing Then
                If Regex.IsMatch(obj.ToString, "\d+") Then
                    Me.p_PortalId = Integer.Parse(obj.ToString, Globalization.NumberStyles.Integer)
                End If
            Else
                context.Response.End()
            End If

            obj = context.Request.Params("blogid")
            If Not obj Is Nothing Then
                If Regex.IsMatch(obj.ToString, "\d+") Then
                    Me.p_BlogId = Integer.Parse(obj.ToString, Globalization.NumberStyles.Integer)
                End If
            Else
                context.Response.End()
            End If

            obj = context.Request.Params("entrylimit")
            If Not obj Is Nothing Then
                If Regex.IsMatch(obj.ToString, "\d+") Then
                    Me.p_EntryLimit = Integer.Parse(obj.ToString, Globalization.NumberStyles.Integer)
                End If
            End If

            obj = context.Request.Params("categoryid")
            If Not obj Is Nothing Then
                If Regex.IsMatch(obj.ToString, "\d+") Then
                    Me.p_CategoryId = Integer.Parse(obj.ToString, Globalization.NumberStyles.Integer)
                End If
            End If

            obj = context.Request.Params("output")
            If Not obj Is Nothing Then
                If Regex.IsMatch(obj.ToString, "^[atom|rss]$", RegexOptions.IgnoreCase) Then
                    Me.p_FeedOutput = obj.ToString
                End If
            End If

            obj = context.Request.Params("copyright")
            If Not obj Is Nothing Then
                If Regex.IsMatch(obj.ToString, "^[true|false]$", RegexOptions.IgnoreCase) Then
                    Me.p_RemoveCopyright = Boolean.Parse(obj.ToString)
                End If
            End If

            ' set default values

            If String.IsNullOrEmpty(Me.FeedOutput) Then
                Me.p_FeedOutput = "rss"
            End If

            If EntryLimit = Null.NullInteger Then
                Me.p_EntryLimit = 10
            End If

            '
            ' look for cached output first
            '

            Dim strCacheKey As String = GetCacheKey()
            Dim objCache As Object = DotNetNuke.Common.Utilities.DataCache.GetCache(strCacheKey)
            If Not objCache Is Nothing Then
                ' we have cached data, return that
                context.Response.Write(objCache.ToString)
                context.Response.End()
            End If

            '
            ' Begin parsing entries and building the output
            '

            Dim ctlBlog As New BlogController
            Dim ctlEntry As New EntryController
            Dim collEntry As New ArrayList
            Dim collComment As New ArrayList

            Dim oBlog As New BlogInfo
            oBlog = ctlBlog.GetBlog(BlogId)

            If Me.CategoryId > Null.NullInteger Then
                collEntry = ctlEntry.ListAllEntriesByCategory(Me.PortalId, Me.CategoryId)
            Else
                collEntry = ctlEntry.ListAllEntriesByBlog(Me.PortalId)
            End If

            Dim strResponse As String = Null.NullString

            Select Case String.Equals(Me.FeedOutput, "rss", StringComparison.InvariantCultureIgnoreCase)
                Case True
                    strResponse = Me.GetRssFeed(context, oBlog, collEntry, collComment)
                Case Else
                    strResponse = Me.GetAtomFeed(context, oBlog, collEntry, collComment)
            End Select

            If Not Me.RemoveCopyright Then
                strResponse = String.Concat(strResponse, "<!-- RSS feed generated by the XML Utility for the DNN Blog open source project: http://dnnblogxml.codeplex.com/ -->")
            End If

            DotNetNuke.Common.Utilities.DataCache.SetCache(strCacheKey, strResponse, DateTime.Now.AddMinutes(Me.BlogXmlCacheTime))

            context.Response.Write(strResponse)
            context.Response.End()

        End Sub

#Region " Helper Methods "

        Private Function GetAtomFeed(ByRef context As System.Web.HttpContext, ByRef oBlog As BlogInfo, ByRef collEntry As ArrayList, ByRef collComment As ArrayList) As String

            Dim ctlComment As New CommentController
            Dim oEntry As New EntryInfo

            Dim sb As New StringBuilder(500)
            With sb
                .Append("<?xml version=""1.0"" encoding=""UTF-8""?> ")
                .Append("<feed xmlns=""http://www.w3.org/2005/Atom""> ")
                .AppendFormat("<title>{0}</title> ", Me.WrapTextForXml(oBlog.Title))
                .AppendFormat("<subtitle>{0}</subtitle> ", Me.WrapTextForXml(oBlog.Description))
                .AppendFormat("<link href=""{0}"" rel=""self"" /> ", oBlog.SyndicationURL)
                .AppendFormat("<link href=""{0}"" /> ", oBlog.SyndicationURL)
                .AppendFormat("<id>{0}</id> ", oBlog.BlogID)
                .AppendFormat("<updated>{0}</updated> ", DateTime.Now.ToString("R")) '"yyyy-MM-ddThh:mm:ss+00:00"))
                .Append("<author> ")
                .AppendFormat("<name>{0}</name> ", Me.WrapTextForXml(oBlog.UserFullName))
                .AppendFormat("<email>{0}</email> ", oBlog.SyndicationEmail)
                .Append("</author> ")

                If Me.PortalId <> Null.NullInteger AndAlso collEntry.Count > 0 Then
                    Dim intCount As Integer = 1

                    For Each item As Object In collEntry
                        If intCount > Me.EntryLimit Then
                            Exit For
                        End If

                        oEntry = CType(item, EntryInfo)
                        collComment = ctlComment.ListComments(oEntry.EntryID, True)

                        .Append("<entry> ")
                        .AppendFormat("<title>{0}</title> ", Me.WrapTextForXml(oEntry.Title))
                        .AppendFormat("<link href=""{0}"" /> ", EscapeUrl(oEntry.PermaLink))
                        .AppendFormat("<link rel=""alternate"" type=""text/html"" href=""{0}"" /> ", EscapeUrl(oEntry.PermaLink))
                        .AppendFormat("<link rel=""edit"" href=""{0}"" /> ", EscapeUrl(oEntry.PermaLink))
                        .AppendFormat("<id>{0}</id> ", oEntry.EntryID)
                        .AppendFormat("<pubDate>{0}</pubDate> ", oEntry.AddedDate.ToString("R")) '"yyyy-MM-ddThh:mm:ss+00:00"))
                        .AppendFormat("<updated>{0}</updated> ", oEntry.AddedDate.ToString("R")) '"yyyy-MM-ddThh:mm:ss+00:00"))
                        .AppendFormat("<guid isPermaLink=""true"">{0}</guid> ", EscapeUrl(oEntry.PermaLink))
                        .AppendFormat("<slash:comments xmlns:slash=""http://purl.org/rss/1.0/modules/slash/"">{0}</slash:comments> ", collComment.Count)
                        .AppendFormat("<summary>{0}</summary> ", Me.FormatBlogSummary(oEntry.Entry))
                        .Append("</entry> ")

                        intCount += 1
                    Next
                End If

                .Append("</feed> ")
            End With

            Return sb.ToString

        End Function

        Private Function GetRssFeed(ByRef context As System.Web.HttpContext, ByRef oBlog As BlogInfo, ByRef collEntry As ArrayList, ByRef collComment As ArrayList) As String

            Dim ctlComment As New CommentController
            Dim ctlEntry As New EntryController
            Dim ctlCategory As New CategoryController
            Dim collCategory As New List(Of CategoryInfo)
            Dim oEntry As New EntryInfo

            Dim sb As New StringBuilder(500)
            With sb
                .Append("<?xml version=""1.0"" encoding=""UTF-8""?> ")
                .Append("<rss version=""2.0""> ")
                .Append("<channel xmlns:blog=""http://www.dotnetnuke.com/blog/"" xmlns:slash=""http://purl.org/rss/1.0/modules/slash/"" xmlns:trackback=""http://madskills.com/public/xml/rss/module/trackback/""> ")

                .AppendFormat("<title>{0}</title> ", Me.WrapTextForXml(oBlog.Title))
                .AppendFormat("<link>{0}</link> ", oBlog.SyndicationURL)
                .AppendFormat("<description>{0}</description> ", Me.WrapTextForXml(oBlog.Description))
                .AppendFormat("<language>{0}</language> ", "en-US")
                .AppendFormat("<webMaster>{0}</webMaster> ", oBlog.SyndicationEmail)
                .AppendFormat("<pubDate>{0}</pubDate> ", DateTime.Now.ToString("R")) '"yyyy-MM-ddThh:mm:ss+00:00"))
                .AppendFormat("<lastBuildDate>{0}</lastBuildDate> ", DateTime.Now.ToString("R")) '"yyyy-MM-ddThh:mm:ss+00:00"))
                .Append("<generator>XML Utility for DNN Blog Module</generator> ")
                .Append("<docs>http://backend.userland.com/rss</docs> ")
                .AppendFormat("<ttl>{0}</ttl> ", Me.BlogXmlCacheTime)

                If Me.PortalId <> Null.NullInteger AndAlso collEntry.Count > 0 Then
                    Dim intCount As Integer = 1
                    Dim trackbackUrl As String = String.Empty

                    For Each item As Object In collEntry
                        If intCount > Me.EntryLimit Then
                            Exit For
                        End If

                        oEntry = CType(item, EntryInfo)
                        collComment = ctlComment.ListComments(oEntry.EntryID, True)
                        trackbackUrl = String.Concat(context.Request.Url.GetLeftPart(UriPartial.Authority), "/DesktopModules/Blog/Trackback.aspx?id=", oEntry.EntryID.ToString)

                        .Append("<item> ")
                        .AppendFormat("<link>{0}</link> ", EscapeUrl(oEntry.PermaLink))
                        .AppendFormat("<title>{0}</title> ", Me.WrapTextForXml(oEntry.Title))
                        .AppendFormat("<description>{0}</description> ", Me.FormatBlogSummary(oEntry.Entry))
                        .AppendFormat("<author>{0}</author> ", oBlog.SyndicationEmail)
                        .AppendFormat("<slash:comments>{0}</slash:comments> ", collComment.Count)
                        .AppendFormat("<guid isPermaLink=""true"">{0}</guid> ", EscapeUrl(oEntry.PermaLink))
                        .AppendFormat("<pubDate>{0}</pubDate> ", oEntry.AddedDate.ToString("R")) '"yyyy-MM-ddThh:mm:ss+00:00"))
                        .AppendFormat("<trackback:ping>{0}</trackback:ping> ", trackbackUrl)
                        .AppendFormat("<comments>{0}#Comments</comments> ", EscapeUrl(oEntry.PermaLink))

                        collCategory = CategoryController.ListCatsByEntry(oEntry.EntryID)
                        If Not collCategory Is Nothing AndAlso collCategory.Count > 0 Then
                            For Each oCategory As CategoryInfo In collCategory
                                Dim strCategoryUrl As String = Regex.Replace(oEntry.PermaLink, "(.*/tabid/\d+/)(.*)", "$1catid/{0}/Default.aspx")
                                strCategoryUrl = String.Format(strCategoryUrl, oCategory.CatId)
                                .AppendFormat("<category domain=""{0}"">{1}</category> ", strCategoryUrl, oCategory.Category)
                            Next
                        End If

                        .Append("</item> ")

                        '<item> 
                        '  <title>Photos from OpenForce EU 2009</title> 
                        '  <link>http://www.dotnetnuke.com/Community/Blogs/tabid/825/EntryId/2388/Photos-from-OpenForce-EU-2009.aspx</link> 
                        '  <description>&lt;p&gt;Here are some photographs of the community team from the recent OpenForce EU conference.&lt;/p&gt;</description> 
                        '  <author>dotnetnuke@alexshirley.com</author> 
                        '  <comments>http://www.dotnetnuke.com/Community/Blogs/tabid/825/EntryId/2388/Photos-from-OpenForce-EU-2009.aspx#Comments</comments> 
                        '  <slash:comments>2</slash:comments> 
                        '  <guid isPermaLink="true">http://www.dotnetnuke.com/Community/Blogs/tabid/825/EntryId/2388/Photos-from-OpenForce-EU-2009.aspx</guid> 
                        '  <pubDate>Sat, 24 Oct 2009 12:12:00 GMT</pubDate> 
                        '  <trackback:ping>http://www.dotnetnuke.comDesktopModules/BlogTrackback.aspx?id=2388</trackback:ping> 
                        '  <category domain="http://www.dotnetnuke.com/Community/Blogs/tabid/825/CatID/16/Default.aspx">Community</category> 
                        '</item> 

                        intCount += 1
                    Next
                End If

                .Append("</channel> </rss> ")
            End With

            Return sb.ToString

        End Function

        ''' <summary>
        ''' EscapeUrl - this method prevents any XML errors from any characters that might be in the parameter value
        ''' </summary>
        ''' <param name="url"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        ''' <history>
        ''' </history>
        Private Function EscapeUrl(ByVal url As String) As String
            If Not String.IsNullOrEmpty(url) Then
                If Not Regex.IsMatch(url, "&amp;") Then
                    url = Regex.Replace(url, "&", "&amp;")
                End If
                If Not Regex.IsMatch(url, "&apos;") Then
                    url = Regex.Replace(url, "'", "&apos;")
                End If
                If Not Regex.IsMatch(url, "&quot;") Then
                    url = Regex.Replace(url, """", "&quot;")
                End If
                If Not Regex.IsMatch(url, "&lt;") Then
                    url = Regex.Replace(url, "<", "&lt;")
                End If
                If Not Regex.IsMatch(url, "&gt;") Then
                    url = Regex.Replace(url, ">", "&gt;")
                End If
            End If

            Return url
        End Function

        Private Function FormatBlogSummary(ByVal Summary As String) As String

            Summary = System.Web.HttpUtility.HtmlDecode(Summary)

            ' if there's a web context, we can gather portal information
            If Not System.Web.HttpContext.Current Is Nothing Then
                Summary = Regex.Replace(Summary, "\ssrc=""/Portals/", String.Format(" src=""{0}/Portals/", Me.GetBaseUrl))
            End If

            Summary = System.Web.HttpUtility.HtmlEncode(Summary)

            Return Summary

        End Function

        Private Function GetBaseUrl() As String

            Return String.Concat("http://", DotNetNuke.Entities.Portals.PortalController.GetCurrentPortalSettings.PortalAlias.HTTPAlias)

        End Function

        Private Function WrapTextForXml(ByVal Text As String) As String

            If String.IsNullOrEmpty(Text) Then
                Return Null.NullString
            End If

            Return String.Format("<![CDATA[{0}]]>", Text)

        End Function

        Private Function GetCacheKey() As String
            Return String.Format("BlogXmlCacheKey_{0}_{1}_{2}_{3}_{4}_{5}", Me.PortalId, Me.BlogId, Me.EntryLimit, Me.CategoryId, Me.FeedOutput, Me.RemoveCopyright)
        End Function

#End Region

#Region " IHttpHandler Implementation "

        Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property

#End Region

    End Class

End Namespace